package prickworlds;

import static org.lwjgl.opengl.GL11.*;

import java.awt.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.WaveData;

import prickworlds.game.Game;
import prickworlds.graphics.Font;
import prickworlds.gui.Control;
import prickworlds.structure.MouseEvent;
import prickworlds.structure.KeyEvent;
import prickworlds.structure.ProgramState;
import prickworlds.util.Path;

/**
 * Main class of the Prickworlds application.
 * 
 * @author Nicolas Winkler
 */
public class Prickworlds extends AppInfo {
	/**
	 * the current {@link ProgramState} running
	 */
	private static ProgramState programState;

	/*
	 * program states
	 */
	private static Game game;
	private static MainMenu mainMenu;

	/**
	 * the main font of the project
	 */
	static Font mainFont;

	/**
	 * {@code true}, when the application is about to close
	 */
	private static boolean closeRequested;

	/**
	 * private constructor
	 */
	private Prickworlds() {
	}

	/**
	 * initializes the variables that use the lwjgl library to initialize this
	 * library has to be initialized itself, so we have to wait until this is
	 * done
	 */
	private static void initialize() {
		try {
			InputStream in /*
							 * = Prickworlds.class
							 * .getResourceAsStream("data/MS_Gothic_80.wbf");
							 * in.close(); in
							 */
			= Prickworlds.getResourceStream("fonts/MS_Gothic_80.wbf");
			mainFont = Font.loadFont(in);
			in.close();
		} catch (IOException e) {
			System.err.println("Error reading font file");
		}

		Control.setFont(mainFont);

		game = new Game();
		mainMenu = new MainMenu();
		programState = game;
		programState = mainMenu;
	}

	private static void loadLibrary(String name) {
		try {
			System.loadLibrary(name);
		} catch (Throwable t) {
			// System.err.println("Error loading library \"" + name
			// + "\" (nothing to worry about)");
			t.printStackTrace();
			// Too bad...
		}
	}

	private static void loadLibraries() {
		final String[] libraries = new String[] { "native/windows/opengl.dll",
				"native/windows/jinput-dx8.dll",
				"native/windows/jinput-dx8_64.dll",
				"native/windows/jinput-raw.dll",
				"native/windows/jinput-raw_64.dll", "native/windows/lwjgl.dll",
				"native/windows/lwjgl64.dll", "native/windows/OpenAL32.dll",
				"native/windows/OpenAL64.dll" };

		String jarLoc = Prickworlds.class.getProtectionDomain().getCodeSource()
				.getLocation().getPath();
		Path path = new Path(jarLoc);
		path.removeLast();

		path.add(new Path("native/windows"));
		System.setProperty("java.library.path", path.getString());
		System.out
				.println("libpath=" + System.getProperty("java.library.path"));
		jarLoc = new File(jarLoc).getPath();

		for (int i = 0; i < libraries.length; i++) {
			// loadLibrary(libraries[i]);
		}
	}

	/**
	 * creates an input stream for game resources in the data folder
	 * 
	 * @param path
	 * @return
	 * @throws FileNotFoundException
	 */
	public static InputStream getResourceStream(String path)
			throws FileNotFoundException {
		return new FileInputStream(new File("data/" + path));
	}

	/**
	 * closes the application
	 */
	public static void requestClose() {
		closeRequested = true;
	}

	/**
	 * The main routine of the Game.
	 */
	public static void run() {

		final int sleepMillis = 20;
		long lastFrame = System.currentTimeMillis();
		while (!Display.isCloseRequested() && !closeRequested) {
			checkKeyEvents();
			checkMouseEvents();

			programState.update(); // programState.update();programState.update();programState.update();
			programState.display();

			try {
				long now = System.currentTimeMillis();
				if (now - lastFrame < sleepMillis) {
					Thread.sleep(sleepMillis - (now - lastFrame));
				}
				lastFrame = now;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			Display.update();
		}
		Display.destroy();
		System.exit(0);
	}

	/**
	 * @return the {@link Game} of this application
	 */
	public static Game getGame() {
		return game;
	}

	/**
	 * resets the game to a new game
	 */
	public static void resetGame() {
		game = new Game();
	}

	/**
	 * @return the {@link MainMenu} of this application
	 */
	public static MainMenu getMainMenu() {
		return mainMenu;
	}

	/**
	 * @param programState
	 *            the new {@link ProgramState}
	 */
	public static void setProgramState(ProgramState programState) {
		Prickworlds.programState = programState;
	}

	/**
	 * checks for keyboard inputs and sends events
	 */
	private static void checkKeyEvents() {
		while (Keyboard.next()) {
			KeyEvent event = new KeyEvent();
			event.key = Keyboard.getEventKey();
			event.type = Keyboard.getEventKeyState() ? KeyEvent.Type.PRESSED
					: KeyEvent.Type.RELEASED;

			programState.handleKeyEvent(event);
		}
	}

	/**
	 * checks for mouse inputs and sends events
	 */
	private static void checkMouseEvents() {
		while (Mouse.next()) {
			MouseEvent event = new MouseEvent();
			int button = Mouse.getEventButton();
			if (button == 0)
				event.type = Mouse.getEventButtonState() ? MouseEvent.Type.LEFT_DOWN
						: MouseEvent.Type.LEFT_UP;
			else if (button == 1)
				event.type = Mouse.getEventButtonState() ? MouseEvent.Type.RIGHT_DOWN
						: MouseEvent.Type.RIGHT_UP;
			else if (button == -1)
				event.type = MouseEvent.Type.MOVE;

			event.x = Mouse.getX();
			event.y = Display.getHeight() - Mouse.getY();

			programState.handleMouseEvent(event);
		}
	}

	/**
	 * creates the display and sets it to fullscreen
	 */
	private static void setupDisplay() {
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Dimension screenSize = toolkit.getScreenSize();

		try {
			DisplayMode[] displayModes = Display.getAvailableDisplayModes();

			ArrayList<DisplayMode> usefulModes = new ArrayList<DisplayMode>();

			for (int i = 0; i < displayModes.length; i++) {
				DisplayMode mode = displayModes[i];
				if (mode.getWidth() == screenSize.width
						&& mode.getHeight() == screenSize.height) {
					usefulModes.add(mode);
				}
			}

			if (usefulModes.isEmpty()) {
				System.err.println("Could not create display.");
			} else {
				Display.setDisplayMode(usefulModes.get(/*
														 * usefulModes.size() -
														 * 1
														 */0));
			}

			Display.setFullscreen(true);
			Display.setVSyncEnabled(true);
			Display.create();

			Display.setTitle(getFullName());

			Display.sync(60);
		} catch (LWJGLException e) {
			// TODO write catch-block
		}
	}

	/**
	 * sets the openGl perspective
	 */
	private static void setupOpenGl() {
		org.lwjgl.opengl.GL11.glOrtho(0.0, Display.getWidth(),
				Display.getHeight(), 0.0, 0.0, 1.0);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}

	/**
	 * initializes the lwjgl library
	 */
	private static void initializeLwjgl() {
		setupDisplay();
		setupOpenGl();
	}

	/**
	 * @param args
	 *            arguments...
	 */
	public static void main(String[] args) {

		

		// System.setProperty("org.lwjgl.librarypath", new
		// File("natives/windows").getAbsolutePath());
		// loadLibraries();
		
		initializeLwjgl();
		initialize();
		soundInit();
		run();
	}
	
	public static void soundInit(){
		
		try {
			AL.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
		 
		IntBuffer buffer = BufferUtils.createIntBuffer(1);
		IntBuffer source = BufferUtils.createIntBuffer(1);
		
		buffer.flip();
		source.flip();

		AL10.alGenBuffers(buffer);

		
		    
		WaveData waveFile;
		try {
			waveFile = WaveData.create(new BufferedInputStream(new FileInputStream("data/Prickworlds1.wav")));
		} catch (FileNotFoundException e) {
			waveFile = WaveData.create("data/Prickworlds1.wav");
			e.printStackTrace();
		}
		
		AL10.alBufferData(buffer.get(0), waveFile.format, waveFile.data, waveFile.samplerate);
		waveFile.dispose();
		AL10.alGenSources(source);

		FloatBuffer nullPos = BufferUtils.createFloatBuffer(3).put(
				new float[] { 0.0f, 0.0f, 0.0f });

		AL10.alListener(AL10.AL_POSITION, nullPos);
		AL10.alListener(AL10.AL_VELOCITY, nullPos);
		AL10.alListener(AL10.AL_ORIENTATION, (FloatBuffer) BufferUtils.createFloatBuffer(6)
						.put(new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f }).rewind());

		AL10.alSourcei(source.get(0), AL10.AL_BUFFER, buffer.get(0));
		AL10.alSourcef(source.get(0), AL10.AL_PITCH, 1.0f);
		AL10.alSourcef(source.get(0), AL10.AL_GAIN, 1.0f);
		AL10.alSource(source.get(0), AL10.AL_POSITION, nullPos);
		AL10.alSource(source.get(0), AL10.AL_VELOCITY, nullPos);

		AL10.alSourcePlay(source.get(0));
	}
}
